package il.ac.bgu.cs.heuristics.core;

import il.ac.bgu.cs.heuristics.graph.State;
import il.ac.bgu.cs.heuristics.graph.types.IActionGraph;
import il.ac.bgu.cs.heuristics.graph.types.IPath;
import il.ac.bgu.cs.heuristics.graph.types.IState;
import il.ac.bgu.cs.heuristics.graph.types.ISubstitution;
import il.ac.bgu.cs.heuristics.utils.CollectionUtils;

import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Set;

public class HeuristicEngineLightIterativeWithDecisionNodesSecondtry extends
		heuristicsEngine implements IHeuristicsEngine {

	private IState	init			= null;

	static int		forkcounter		= 0;
	static int		Searchcounter	= 0;

	public HeuristicEngineLightIterativeWithDecisionNodesSecondtry(
			IActionGraph aGraph) {
		super(aGraph);
		// TODO Auto-generated constructor stub
	}

	@Override
	public int getHValue(IState init, IState goal) {
		this.init = init;
		aGraph.setInitState(init);
		Queue<SearchNode> queue = new PriorityQueue<SearchNode>();
		SearchNode originalGoal = new SearchNode(init
				.getNotContainedSubstitution(goal.getSubstitutions()), null,
				null, 0, 0);
		ForkSearchNode originalGoalAsFork = new ForkSearchNode(originalGoal);
		// ugly ugly hack for the original goal
		originalGoalAsFork.setAsOriginalGoal();
		Set<IState> subgoals = aGraph.getNonContradictingEffects(goal);
		subgoals = filterGoals(init, goal, subgoals,
				new NotUsefulGoalsStateFilter());
		Set<ISubstitution> goalHack = CollectionUtils.newLinkedHashSet();
		for (IState state : subgoals) {
			goalHack.addAll(state.getSubstitutions());
		}
		originalGoalAsFork.coverAllProposition = goalHack.containsAll(goal
				.getSubstitutions());

		queue.add(originalGoalAsFork);
		SearchNode currentNode = null;
		while (!queue.isEmpty() && !originalGoalAsFork.isDone()) {
			currentNode = queue.poll();
			if (currentNode.isReachedInit(init)) {
				currentNode.notifyParentOnCompletion();
				continue;
			}
			Set<SearchNode> sonsSet = currentNode.createSons(aGraph);
			queue.addAll(sonsSet);
		}

		log("Node counter: " + Searchcounter);
		log("Fork counter: " + forkcounter);
		return originalGoalAsFork.hValue;
	}

	private class SearchNode implements Comparable {
		int				level;
		int				hValue;
		IState			goal		= null;
		IState			firstEffect	= null;
		ForkSearchNode	parentNode	= null;

		public SearchNode(Set<ISubstitution> newGoalSubstitutions,
				ForkSearchNode parentNode, IState firstEffect, int level,
				int hvalue) {
			this.firstEffect = firstEffect;
			this.goal = State.createState(newGoalSubstitutions);
			this.hValue = hvalue;
			this.level = level;
			this.parentNode = parentNode;
			Searchcounter++;
		}

		public boolean isReachedInit(IState init) {
			return init.contains(goal);
		}

		public boolean isOriginalGoal() {
			return firstEffect == null;
		}

		boolean isForkNode(Set<IState> setOfEffects) {
			if (this.isOriginalGoal())
				return false;
			for (IState effects : setOfEffects) {
				if (effects.getSubstitutions().containsAll(
						this.goal.getSubstitutions()))
					return false;
			}
			return true;

		}

		Set<SearchNode> createSons(IActionGraph aGraph) {
			Set<SearchNode> sonsSet = CollectionUtils.newLinkedHashSet();
			Set<IState> subgoals = aGraph.getNonContradictingEffects(this.goal);
			subgoals = filterGoals(init, this.goal, subgoals,
					new NotUsefulGoalsStateFilter());

			SearchNode newCurrentNode = null;
			if (this.isOriginalGoal()) {
				newCurrentNode = this;
			} else {
				boolean isCurrentNodeIsFork = isForkNode(subgoals);
				newCurrentNode = !isCurrentNodeIsFork ? this
						: new ForkSearchNode(this);

			}

			for (IState sonGoal : subgoals) {
				SearchNode sonSearchNode = newCurrentNode.createSon(aGraph,
						sonGoal);
				if (sonSearchNode != null)
					sonsSet.add(sonSearchNode);
			}
			return sonsSet;
		}

		// SearchNode createSon

		SearchNode createSon(IActionGraph aGraph, IState newSonGoal) {
			IPath path = aGraph.getShortestPath(init, newSonGoal);
			Set<ISubstitution> ConstraintsList = path.getConstraints();
			if (path.isContradictingPath())
				return null;
			ConstraintsList = init.getNotContainedSubstitution(ConstraintsList);
			return new SearchNode(ConstraintsList, this.parentNode,
					this.firstEffect, this.level + 1, this.hValue
							+ path.weight());

		}

		private void notifyParentOnCompletion() {
			this.parentNode.notifyParentOnCompletion(this);
		}

		@Override
		public String toString() {
			return goal.toString() + " level: " + level + " hvalue " + hValue;
		}

		public int compareTo(Object otherNode) {

			return (this.goal.getSubstitutions().size() * this.level)
					- ((SearchNode) otherNode).goal.getSubstitutions().size()
					* ((SearchNode) otherNode).level;
		}
	}

	public class ForkSearchNode extends SearchNode {
		private Set<ISubstitution>	reachSubstitution	= null;
		boolean						done				= false;
		boolean						coverAllProposition	= true;
		int							maxSonHvalue		= 0;

		ForkSearchNode(SearchNode node) {
			super(node.goal.getSubstitutions(), node.parentNode,
					node.firstEffect, node.level, node.hValue);
			this.reachSubstitution = CollectionUtils.newLinkedHashSet();
			forkcounter++;
		}

		public boolean isDone() {
			return done;
		}

		@Override
		SearchNode createSon(IActionGraph aGraph, IState newSonGoal) {
			IPath path = aGraph.getShortestPath(init, newSonGoal);
			Set<ISubstitution> ConstraintsList = path.getConstraints();
			if (path.isContradictingPath())
				return null;
			ConstraintsList = init.getNotContainedSubstitution(ConstraintsList);
			return new SearchNode(ConstraintsList, this, newSonGoal,
					this.level + 1, path.weight());
		}

		protected void notifyParentOnCompletion(SearchNode childNode) {

			if (childNode != this) {
				if (!reachSubstitution.containsAll(childNode.firstEffect
						.getSubstitutions())) {
					reachSubstitution.addAll(childNode.firstEffect
							.getSubstitutions());

					// this.maxSonHvalue = Math.max(maxSonHvalue,
					// childNode.hValue);
					this.hValue += childNode.hValue;
				}
			}
			if (!amIDone()) // TODO add the logic to decide whether or not this
			// fork node is done
			{
				return;
			}

			// this node is done.
			this.done = true;
			// this.hValue += maxSonHvalue;
			if (!this.isOriginalGoal())
				this.parentNode.notifyParentOnCompletion(this);
		}

		private boolean amIDone() {
			return (reachSubstitution.containsAll(goal.getSubstitutions()) || !this.coverAllProposition);
		}

		@Override
		public String toString() {
			return super.toString() + reachSubstitution.toString();
		}

		@Override
		boolean isForkNode(Set<IState> setOfEffects) {
			// TODO: add logic to decide which is fork node and which is not
			return true;
		}

		void setAsOriginalGoal() {
			this.parentNode = this;
		}

	}

}
